//The MIT License

//Copyright (c) 2009 Lluis Sanchez, Nick Darnell

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

using System;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;

using Polymorph.Properties;

namespace Polymorph
{
    public class CodeDynamicMethod
    {
        protected DynamicMethod m_method;
        protected Type m_owner;
        protected Type[] m_parameterTypes;
        protected CodeBuilder m_builder;

        #region Public Constructors

        public CodeDynamicMethod(string name)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes);
        }

        public CodeDynamicMethod(Module m, string name)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, m);
        }

        public CodeDynamicMethod(Type owner, string name)
        {
            m_owner = owner;
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, owner);
        }

        public CodeDynamicMethod(string name, Type returnType)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes);
        }

        public CodeDynamicMethod(Module m, string name, bool skipVisibility)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, m, skipVisibility);
        }

        public CodeDynamicMethod(Type owner, string name, bool skipVisibility)
        {
            m_owner = owner;
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, owner, skipVisibility);
        }

        public CodeDynamicMethod(string name, Type returnType, bool skipVisibility)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes, skipVisibility);
        }

        public CodeDynamicMethod(Module m, string name, Type returnType)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes, m);
        }

        public CodeDynamicMethod(Type owner, string name, Type returnType)
        {
            m_owner = owner;
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes, owner);
        }

        public CodeDynamicMethod(Module m, string name, Type returnType, bool skipVisibility)
        {
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes, m, skipVisibility);
        }

        public CodeDynamicMethod(Type owner, string name, Type returnType, bool skipVisibility)
        {
            m_owner = owner;
            m_parameterTypes = Type.EmptyTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, m_parameterTypes, owner, skipVisibility);
        }

        public CodeDynamicMethod(string name, Type[] parameterTypes)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes);
        }

        public CodeDynamicMethod(Module m, string name, Type[] parameterTypes)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, m);
        }

        public CodeDynamicMethod(Type owner, string name, Type[] parameterTypes)
        {
            m_owner = owner;
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, typeof(void), m_parameterTypes, owner);
        }

        public CodeDynamicMethod(string name, Type returnType, Type[] parameterTypes)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes);
        }

        public CodeDynamicMethod(Module m, string name, Type returnType, Type[] parameterTypes)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes, m);
        }

        public CodeDynamicMethod(Type owner, string name, Type returnType, Type[] parameterTypes)
        {
            m_owner = owner;
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes, owner);
        }

        public CodeDynamicMethod(string name, Type returnType, Type[] parameterTypes, bool skipVisibility)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes, skipVisibility);
        }

        public CodeDynamicMethod(Module m, string name, Type returnType, Type[] parameterTypes, bool skipVisibility)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes, m, skipVisibility);
        }

        public CodeDynamicMethod(Type owner, string name, Type returnType, Type[] parameterTypes, bool skipVisibility)
        {
            m_owner = owner;
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, returnType, parameterTypes, owner, skipVisibility);
        }

        public CodeDynamicMethod(Module m, string name, MethodAttributes attributes, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, bool skipVisibility)
        {
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, attributes, callingConvention, returnType, parameterTypes, m, skipVisibility);
        }

        public CodeDynamicMethod(Type owner, string name, MethodAttributes attributes, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, bool skipVisibility)
        {
            m_owner = owner;
            m_parameterTypes = parameterTypes;
            m_builder = new CodeBuilder();
            m_method = new DynamicMethod(name, attributes, callingConvention, returnType, parameterTypes, owner, skipVisibility);
        }

        #endregion

        public string Name
        {
            get { return m_method.Name; }
        }

        public MethodAttributes Attributes
        {
            get { return m_method.Attributes; }
        }

        public Type ReturnType
        {
            get { return m_method.ReturnType; }
        }

        public Type[] ParameterTypes
        {
            get { return m_parameterTypes; }
        }

        public bool IsStatic
        {
            get { return m_method.IsStatic; }
        }

        public CodeBuilder CodeBuilder
        {
            get { return m_builder; }
        }

        public string PrintCode()
        {
            StringWriter sw = new StringWriter();
            CodeWriter cw = new CodeWriter(sw);
            PrintCode(cw);
            return sw.ToString();
        }

        public void PrintCode(CodeWriter cp)
        {
            cp.BeginLine();
            if ((Attributes & MethodAttributes.Static) != 0)
                cp.Write("static ");
            if (ReturnType != null) cp.Write(ReturnType + " ");
            cp.Write(Name + " (");
            for (int n = 0; n < ParameterTypes.Length; n++)
            {
                if (n > 0) cp.Write(", ");
                cp.Write(ParameterTypes[n] + " arg" + n);
            }
            cp.Write(")");
            cp.EndLine();
            cp.WriteLineInd("{");

            CodeBuilder.PrintCode(cp);

            cp.WriteLineUnind("}");
        }

        /// <summary>
        /// Gets a reference to an argument passed into this method.
        /// </summary>
        /// <param name="index">The argument index.</param>
        /// <returns></returns>
        public CodeArgumentReference GetArg(int index)
        {
            if (index < 0 || index >= ParameterTypes.Length)
            {
                throw new InvalidOperationException(
                    string.Format(Resources.InvalidArgumentNumber0, index));
            }

            int narg = IsStatic ? index : index + 1;
            return new CodeArgumentReference(ParameterTypes[index], narg, "arg" + index);
        }

        /// <summary>
        /// Gets a reference to the 'this' object that the method belongs to.
        /// </summary>
        /// <returns>A reference to the owner object.</returns>
        public CodeArgumentReference GetThis()
        {
            if (IsStatic)
                throw new InvalidOperationException(Resources.NoThisInStaticMethods);

            return new CodeArgumentReference(m_owner, 0, "this");
        }

        public DynamicMethod Generate()
        {
            ILGenerator gen = m_method.GetILGenerator();

            Label returnLabel = gen.DefineLabel();
            CodeBuilder.ReturnLabel = returnLabel;
            CodeBuilder.Generate(gen);
            gen.MarkLabel(returnLabel);
            gen.Emit(OpCodes.Ret);

            return m_method;
        }
    }
}